#pragma once
#include <queue>
#include <condition_variable>
#include <mutex>
#include <chrono>
#include <functional>
#include <memory>
#include <thread>
#include <vector>

struct Message final
{
	int id;
	bool isPriority;
	long long executeAt;
	std::function<void(const Message&)> task;

	explicit Message() : id(0), isPriority(false), executeAt(0), task([](const Message&) {}) {};

	explicit Message(std::function<void(const Message&)> task, int id = 0, bool p = false, long delayMs = 0) :
	task(task), id(id), isPriority(p), executeAt(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count() + delayMs){};

	Message(const Message& other) : id(other.id), isPriority(other.isPriority), executeAt(other.executeAt), task(other.task) {};

	Message(Message&& other) noexcept : id(other.id), isPriority(other.isPriority), executeAt(other.executeAt), task(std::move(other.task))
	{
		other.id = 0;
		other.isPriority = false;
		other.executeAt = 0;
	};

	Message& operator=(const Message& other)
	{
		id = other.id;
		task = other.task;
		executeAt = other.executeAt;
		isPriority = other.isPriority;		
	}

	Message& operator=(Message&& other) noexcept
	{
		if (this != &other)
		{
			id = other.id;
			task = std::move(other.task);
			executeAt = other.executeAt;
			isPriority = other.isPriority;
			other.id = 0;
			other.executeAt = 0;
			other.isPriority = false;
		}
		return *this;
	}

	std::chrono::steady_clock::time_point getExecuteAt() const
	{
		return std::chrono::time_point<std::chrono::steady_clock>(std::chrono::milliseconds(executeAt));
	}

	bool shouldExecute() const
	{
		return isPriority || (getExecuteAt() < std::chrono::steady_clock::now());
	}
};

class MessageQueue final
{
private:

	struct CompareExecuteTime
	{
		bool operator()(const Message& a, const Message& b) const
		{
			return a.getExecuteAt() > b.getExecuteAt();
		}
	};

	std::priority_queue<Message, std::vector<Message>, CompareExecuteTime> queue;
	mutable std::mutex mtx;
	std::condition_variable condition;
	bool stop = false;

public:
	void enqueue(const Message& msg);
	Message dequeue();
	bool empty() const;
	void quit();
	void clear();
};

class Looper final
{
private:
	MessageQueue queue;
	std::thread worker;
	bool isRunning = true;

public:
	Looper();
	~Looper();

	void enquee(int what, long delay, std::function<void(const Message&)> task);

	void enqueePriority(int what, long delay, std::function<void(const Message&)> task);

	void loop();

	void removeAllMessages();
	
	void quit();

};

